//------------------------------------------------------------------------------
// <copyright file="RpcBuiltInHooks.cpp" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

#include "stdafx.h"

#include "RpcServer.h"
#include "RpcServerImpl.h"
#include "RpcArchiveImpl.h"
#include "RpcBuiltInHooks.h"
#include <algorithm>

namespace Microsoft { namespace Internal { namespace GamesTest { namespace Rpc { namespace Server
{
	HRESULT SubscribeToEvents(RpcArchiveImpl& input, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server);
	HRESULT ReportServerPlatformAndArchitecture(RpcArchiveImpl& output);

	RpcHookCode SetMaximumQueueSize(RpcArchiveImpl& input, RpcArchiveImpl& output, RpcServerImpl* server)
	{
		UNREFERENCED_PARAMETER(output);

		HRESULT hr = E_FAIL;

		unsigned int size;
		CHK(input.GetNamedValue(L"maximumQueueSize", &size));

		server->m_maxQueueSize = size;

	end:
		return hr;
	}

	RpcHookCode UpdateEventSubscription(RpcArchiveImpl& input, RpcArchiveImpl& output, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server)
	{
		UNREFERENCED_PARAMETER(output);

		HRESULT hr = E_FAIL;
		wchar_t* buffer = nullptr;
		DWORD stringLength = 0;
		DWORD charactersWritten = 0;
		std::wstring eventName;
		bool subscribe = false;

		CHK(input.MeasureNamedStringValue(L"eventName", &stringLength));

		buffer = new wchar_t[stringLength];
		CHK(input.GetNamedStringValue(L"eventName", buffer, stringLength, &charactersWritten));

		eventName = buffer;

		CHK(input.GetNamedValue(L"subscribe", &subscribe));

		CHK(UpdateSingleEvent(client, server, eventName, subscribe));

	end:
		if (buffer != nullptr)
		{
			delete[] buffer;
		}
		return hr;
	}

	RpcHookCode UpdateSingleEvent(std::weak_ptr<IRpcChannel> client, RpcServerImpl* server, const std::wstring& eventName, bool subscribe)
	{
		HRESULT hr = S_OK;
		std::pair<RpcServerImpl::RpcSubscriberTable::iterator, RpcServerImpl::RpcSubscriberTable::iterator> range;
		std::shared_ptr<IRpcChannel> clientSharedPtr = nullptr;

		range = server->m_subscriberTable.equal_range(eventName);
		clientSharedPtr = client.lock();
		if (clientSharedPtr == nullptr)
		{
			hr = GAMESTEST_RPC_E_CLIENT_DISCONNECTED;
			goto end;
		}

		if (subscribe)
		{
			if (!std::any_of(range.first, range.second, [=](std::pair<std::wstring, std::weak_ptr<IRpcChannel>> pair)
			{
				std::shared_ptr<IRpcChannel> iteratorSharedPtr = pair.second.lock();
				return (iteratorSharedPtr != nullptr && clientSharedPtr.get() == iteratorSharedPtr.get());
			}))
			{
				server->m_subscriberTable.insert(RpcServerImpl::RpcSubscriberTable::value_type(eventName, client));
			}
		}
		else
		{
			for(RpcServerImpl::RpcSubscriberTable::iterator iter = range.first; iter != range.second; iter++)
			{
				std::shared_ptr<IRpcChannel> iteratorSharedPtr = (*iter).second.lock();
				if (iteratorSharedPtr != nullptr && clientSharedPtr.get() == iteratorSharedPtr.get())
				{
					server->m_subscriberTable.erase(iter);
					break;
				}
			}
		}

	end:
		return hr;
	}

	RpcHookCode VerifyPropertiesAndSubscribeOnConnect(RpcArchiveImpl& input, RpcArchiveImpl& output, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server)
	{
		HRESULT hr = SubscribeToEvents(input, client, server);

		if (FAILED(hr))
		{
			return hr;
		}

		hr = ReportServerPlatformAndArchitecture(output);

		if (FAILED(hr))
		{
			return hr;
		}

		return S_OK;
	}

	HRESULT SubscribeToEvents(RpcArchiveImpl& input, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server)
	{
		// Underlying infrastructure does verification of server ID and version information.
		HRESULT hr = S_OK;
		DWORD numEventNames = 0;
		DWORD numTotalCharacters = 0;
		wchar_t** eventPtrs = nullptr;
		wchar_t* stringContents = nullptr;

		CHK(input.MeasureNamedStringCollection(L"events", &numEventNames, &numTotalCharacters));

		if (numEventNames == 0)
		{
			return hr;
		}

		eventPtrs = new (std::nothrow) wchar_t*[numEventNames];
		stringContents = new (std::nothrow) wchar_t[numTotalCharacters];

		if(!eventPtrs || !stringContents)
		{
			hr = E_OUTOFMEMORY;
			goto end;
		}

		CHK(input.GetNamedStringCollection(L"events", eventPtrs, numEventNames, stringContents, numTotalCharacters));

		for (unsigned int iter = 0; iter < numEventNames; iter ++)
		{
			CHK(UpdateSingleEvent(client, server, eventPtrs[iter], true));
		}

	end:
		if (eventPtrs != nullptr)
		{
			delete [] eventPtrs;
		}

		if (stringContents != nullptr)
		{
			delete [] stringContents;
		}

		return hr;
	}

	HRESULT ReportServerPlatformAndArchitecture(RpcArchiveImpl& output)
	{
		// Set platform based on compiler defines
#ifdef _DURANGO
	#ifdef _WINRT_DLL
		LPCWSTR platform = L"DurangoApp";
	#else
		LPCWSTR platform = L"DurangoGame";
	#endif
#elif defined(_WINRT_DLL)
		LPCWSTR platform = L"StoreApp";
#else
		LPCWSTR platform = L"DesktopApp";    
#endif

		// Set architecture based on compiler defines
#if defined(_WIN64) || defined(_DURANGO)
		LPCWSTR architecture = L"x64";
#else
		LPCWSTR architecture = L"x86";
#endif

		HRESULT hr = output.SetNamedStringValue(L"Platform", platform);

		if (FAILED(hr))
		{
			return hr;
		}

		hr = output.SetNamedStringValue(L"Architecture", architecture);

		if (FAILED(hr))
		{
			return hr;
		}

		return S_OK;
	}

	RpcHookCode EnumRegisteredHooks(RpcArchiveImpl& input, RpcArchiveImpl& output, std::weak_ptr<IRpcChannel> client, RpcServerImpl* server)
	{
		UNREFERENCED_PARAMETER(input);

		HRESULT hr = S_OK;

		for (RpcServerImpl::RpcHookTable::const_iterator pos = server->m_hookTable.begin();
			 pos != server->m_hookTable.end();
			 ++pos)
		{
			// Add known meta-data for each hook
			RpcArchiveImpl hookInfo;
			hr = hookInfo.SetNamedStringValue(L"name", pos->first.data());

			if (FAILED(hr))
			{
				return hr;
			}
			
			hr = output.AddItemToNamedCollection(L"hooks", &hookInfo);

			if (FAILED(hr))
			{
				return hr;
			}
		}

		return hr;
	}

}}}}}